package system.util;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import system.DebugOptionInterface;


/* The class loader for an agent (one instance per agent).
   The class loader translate class names (String) into class 
   objects (Class).

   loadStarter - loads the starter class. The starter class must be
   explicitely loaded by this loader using "defineClass" instead
   of being loaded by the default loader (forName). This 
   ensures that all the classes used by the agents are loaded
   by this loader since the agent is deserialized by the
   starter class.

   loader - the constructor with no parameters is used by the server. 
   The server then integrates the agent code using integrateCode().
   The constructor with the filename parameter is used by the
   shell in order to load the jar file containing the agent
   classes.

   extractCode - return an hastable with associates the code of the class
   with each class name (for the agent classes).

   integrateCode - reassign the above hashtable to the class loader and
   loads all these classes.

   loadClass - the class loader main entry point used by the JVM in
   order to translate class names.
 */


public class Loader extends ClassLoader implements DebugOptionInterface {

	private Hashtable<String, Class> cache = new Hashtable<String, Class>();
	private Hashtable<String, byte[]> code = new Hashtable<String, byte[]>();

	private String classFolder = "bin";
	/* Load the starter class */
	public void loadStarter() {

		/* Open the file */
		File f;
		FileInputStream fis;
		//StarterImpl.class.getName()
		String separator = System.getProperty("file.separator");
		f = new File(classFolder + separator + StarterImpl.class.getName().replaceAll("\\.", separator) + ".class");
		
		if (!f.exists()) {
			System.out.println("Loader(loadStarter) error: class StarterImpl not found");
			return;
		}
		
		int s = (int)f.length();
		byte data[] = new byte[s];
		try {
			//Read the content of the file
			fis = new FileInputStream(f);
			fis.read(data);
			fis.close();

			//Create the class object
			Class c = defineClass(StarterImpl.class.getName(), data, 0, data.length);
			cache.put(StarterImpl.class.getName(), c);
			return;
		} catch (Exception ex) {
			System.out.println("Loader(loadStarter) error: " + ex.getMessage());
			ex.printStackTrace();
			return;
		}
	}

	/* The constructor for the server */
	public Loader() {
	}

	/* The constructor for the shell */
	public Loader(String filename) {
		try {

			/* Open the file as a ZIP file */
			File f = new File(filename + ".jar");
			if (!f.exists()) {
				return;
			}
			ZipFile z = new ZipFile(f);

			/* Parse the classes in the jar */
			Enumeration e = z.entries();
			for (;e.hasMoreElements();) {
				ZipEntry ze = (ZipEntry)e.nextElement();
				String name = ze.getName();
				int size = (int)ze.getSize();

				/* Skip the jar's meta data */
				if (name.startsWith("META")) {
					continue;
				}
				name = name.substring(0, name.lastIndexOf(".class"));//.replaceAll(System.getProperty("file.separator"), ".");
				byte cd[] = new byte[size];
				DataInputStream dis = new DataInputStream(z.getInputStream(ze));
				dis.readFully(cd);

				/* Store the byte code in the hashtable
				   used to migrate the agent code.
				 */
				code.put(name, cd);

				/* Create the class object */
				Class c = defineClass(name, cd, 0, cd.length);
				cache.put(name, c);
			}
		} catch (Exception ex) {
			System.out.println("Loader(loader) error: " + ex.getMessage());
			ex.printStackTrace();
			return;
		}
	}


	/* Extract the agent code */
	public Hashtable<String, byte[]> extractCode() {
		/* Just return the hashtable */
		return code;
	}


	/* Integrate the agent code in the class loader */
	public void integrateCode(Hashtable<String, byte[]> cod) {
		code = cod;
		try {
			/* Parse the hashtable in order to load each class */
			Enumeration<String> e = code.keys();
			for (;e.hasMoreElements();) {
				String n = e.nextElement();
				byte cd[] = code.get(n);
				Class c = defineClass(n, cd, 0, cd.length);
				cache.put(n,c);
			}
		} catch (Exception ex) {
			System.out.println("Loader(integrateCode) error: " + ex.getMessage());
			ex.printStackTrace();
			return;
		}
	}


	/* The class name translator */
	@Override
	public synchronized Class loadClass(String name, boolean resolve) {
		/* Check whether the class is already loaded */
		Class c = cache.get(name);
		if (c != null) {
			return c;
		}

		//If the class was not loaded from the jar file it must be a system class.
		try {
			c = findSystemClass(name);
			cache.put(name, c);
			return c;
		} catch (Exception ex) {
			System.out.println("Loader(loadClass) error: " + ex.getMessage());
			ex.printStackTrace();
			return null;
		}

	}
}


